#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <sstream> //使用stringstream函数进行字符串切分
#include <unordered_map>
#include <algorithm>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include "Util.hpp"
#include "Log.hpp"

//响应的状态码
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500


#define WEB_ROOT "wwwroot" //定义服务器根目录
#define HOME_PAGE "index.html" //当请求行中的url仅仅是/的时候
#define LINE_END "\r\n" //构建的响应数据换行符
#define HTTP_VERSION "HTTP/1.0"
#define PAGE_404 "404.html"
#define PAGE_400 "400.html"
#define PAGE_500 "500.html"


static std::string Code2Desc(int status_code)
{
    std::string codeDesc = "";
    switch (status_code)
    {
    case OK:
        codeDesc = "OK";
        break;
    case NOT_FOUND:
        codeDesc = "NOT_FOUND";
        break;
    default:
        break;
    }
    return codeDesc;
}

static std::string Suffix2Desc(const std::string& suffix)
{
    //该哈希表值被实例化一次，因为是静态变量
    static std::unordered_map<std::string, std::string> suffix2Desc = {
        {".html","text/html"},
        {".css","text/css"},
        {".js","application/x-javascript"},
        {".jpg","application/x-jpg"},
        {".xml","text/xml"}
    };
    //先查找suffix后缀是否在哈希表中存在
    auto iter = suffix2Desc.find(suffix);
    if (suffix2Desc.end() == iter) {
        return "text/html";
    }
    return iter->second;
}

class HttpRequest
{
public:
    std::string request_line;
    std::vector<std::string> request_header;
    std::string blank;
    std::string request_body;

    //Parse请求报头
    std::string method;
    std::string uri;
    std::string version;

    //当请求方法method为GET的时候uri的组成就是：资源路径?参数
    std::string path;
    std::string query_string;

    //存储请求报头的键值对
    std::unordered_map<std::string, std::string> request_header_kv;
    int content_length; //请求正文的字符个数

    bool cgi;

    std::string suffix;
public:
    HttpRequest()
        :content_length(0)
        , cgi(false)
    {}
    ~HttpRequest()
    {}
};
class HttpResponse
{
public:
    std::string status_line;
    std::vector<std::string> response_header;
    std::string blank;
    std::string response_body;
    int status_code;
    int fd;
    size_t size; //当brower请求的资源路路径是HOME_PAGE时的响应正文的长度
    HttpResponse()
        :blank(LINE_END)
        , status_code(OK)
        , fd(-1)
        , size(0)
    {}
    ~HttpResponse()
    {}
};

// 1.读取请求2.分析请求3.构建响应
// IO通信
class EndPoint
{
private:
    int sock;
    bool stop;
    HttpRequest http_request;
    HttpResponse http_response;

private:
    bool RecvHttpRequestLine()
    {
        //直接读取报头会将'\n'也读取到，这样在打印出的日志就很不好看
        auto& line = http_request.request_line; //使用引用line,这样将line传入到下面函数中实参就不长了
        if (Util::ReadLine(sock, line) > 0) {
            line.resize(line.length() - 1);//将http_request.request_line中的'\n'去掉
            LOG(INFO, line);
        }
        else {
            stop = true;
        }
        return stop;
    }
    // void RecvHttpRequestHeader()
    // {
    //     std::string line;
    //     while(line != "\n"){
    //         Util::ReadLine(sock,line);
    //         if(line != "\n"){
    //             line.resize(line.length()-1);//将请求报头信息中的'\n'去掉
    //             http_request.request_header.push_back(line);
    //             LOG(INFO,line);
    //             line.clear();
    //         }
    //     }
    //     if("\n" == line) http_request.blank = line;
    // }

    bool RecvHttpRequestHeader()
    {
        std::string line;
        while (true) {
            line.clear();
            if (Util::ReadLine(sock, line) <= 0) {
                stop = true;
                break;
            }
            if (line == "\n") {
                http_request.blank = line;
                break;
            }
            line.resize(line.size() - 1);
            http_request.request_header.push_back(line);
            LOG(INFO, line);
        }
        return stop;
    }

    void ParseHttpRequestLine()
    {
        const std::string& line = http_request.request_line;
        std::stringstream ss(line);
        ss >> http_request.method >> http_request.uri >> http_request.version;
        // LOG(INFO,http_request.method);
        // LOG(INFO,http_request.uri);
        // LOG(INFO,http_request.version);
        //将请求方法变为大写
        auto& method = http_request.method;
        transform(method.begin(), method.end(), method.begin(), ::toupper);
    }
    void ParseHttpRequestHeader()
    {
        std::string key, value;
        for (const auto& s : http_request.request_header) {
            if (Util::CutString(s, key, value, SEP)) {
                http_request.request_header_kv.insert({ key,value });
            }
        }
    }
    bool IsNeedParseHttpRequestBody()
    {
        auto& method = http_request.method;
        auto& header_kv = http_request.request_header_kv;
        auto& s = "Content-length";
        auto iter = header_kv.find(s);
        if (method == "POST") {
            if (iter != header_kv.end()) { //此时"Content-Length"键对存在，需要读请求报文
                http_request.content_length = stoi(iter->second);
                return true;
            }
        }
        return false;
    }
    bool RecvHttpRequestBody()
    {
        int content_length = http_request.content_length;
        auto& request_body = http_request.request_body;
        if (IsNeedParseHttpRequestBody()) {
            while (content_length) {
                char ch = 0;
                ssize_t n = recv(sock, &ch, 1, 0);
                if (n > 0) {
                    request_body.push_back(ch);
                    --content_length;
                }
                else {
                    stop = true;//执行到读请求正文则说明读取请求行、报头都没出错，但是在读取请求正文的时候可能会出错，则后续代码就不会向browser发送响应了
                    break;
                }
            }
        }
        return stop;
    }

    int ProccessCgi()
    {
        int code = OK;
        //创建两个管道
        int input[2];
        int output[2];
        if (pipe(input) < 0) {
            LOG(ERROR, "pipe input error");
            code = SERVER_ERROR;
            return code;
        }
        if (pipe(output) < 0) {
            LOG(ERROR, "pipe output error");
            code = SERVER_ERROR;
            return code;
        }

        //创建子进程前不能关闭文件描述符，否则子进程无法继承这些文件描述符
        // close(input[1]);
        // close(output[0]);
        //创建子进程进行后续的程序替换

        //
        const auto& method = http_request.method;
        std::string method_env = "";
        const auto& body_text = http_request.request_body; //当请求方法是POST时的用户向服务器提交的参数
        std::string& response_body = http_response.response_body;
        const auto& query_string = http_request.query_string;
        std::string query_string_env = "";
        std::string content_length_env = "";
        //父进程将数据传递给子进程有两种方式：
        //1.通过向管道写数据。然后子进程从管道中读取数据
        //2.通过环境变量。通过putenv方法，该方法是将环境变量设置到进程内部，进行进程替换的进程内部的不会环境变量不变
        //如果请求方法是GET，用户上传的参数数据就比较短，因此直接通过设置环境变量的方式进行向子进程传递参数数据
        pid_t pid = fork();
        if (0 == pid) {
            //child
            close(input[0]);
            close(output[1]);
            dup2(output[0], 0);//程序替换后子进程无法获取父进程中的数组input和output数据(进程之间具有独立性)
            dup2(input[1], 1);//因此要进行重定向。子进程在读取管道相当于读取标准输入;写入管道相当于写入标准输出
            if ("GET" == method) {
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                putenv(const_cast<char*>(query_string_env.c_str()));//由于putenv的形参h是char*而string中的c_str()是const char*的
                //LOG(INFO,"Get Method, Add Query_String_Env");
                //std::cout << query_string_env << std::endl;
            }
            else if ("POST" == method) { //如果是请求方法是POST的话，那么子进程执行被替换的程序就要知道需要读取参数数据的大小
                content_length_env += "CONTENT_LENGTH=";
                content_length_env += std::to_string(http_request.content_length);
                putenv(const_cast<char*>(content_length_env.c_str()));
                LOG(INFO, "Post Method, Add Content_length_env");
            }
            //std::cout << "-------------------------------------" <<std::endl;
            //子进程进行程序替换后，怎么知道是通过读取管道来获取参数数据还是通过环境变量来获取参数数据呢
            //这就要我们把请求方法设置到子进程的环境变量中
            method_env = "METHOD=";
            method_env += method;
            putenv(const_cast<char*>(method_env.c_str()));
            //std::cout << "-------------------------------------" <<std::endl;
            //std::cout << http_request.path.c_str() <<std::endl;
            execl(http_request.path.c_str(), http_request.path.c_str(), nullptr);
            exit(1);
        }
        else if (pid < 0) {
            LOG(ERROR, "fork error");
            code = SERVER_ERROR;
            return code;
        }
        else {
            //parent
            close(input[1]);
            close(output[0]);
            //父进程进行cgi机制时要将数据交给cgi程序进行处理
            if ("POST" == method) {
                size_t size = body_text.length();
                size_t total = 0;
                ssize_t n = 0;
                //处理可能不能一次将数据全部写入管道中
                while (total < size && (n = write(output[1], body_text.c_str() + total, size - n)) > 0) {
                    total += n;
                }
            }

            //从管道中读取cgi机制将query_string处理后的数据
            char ch = 0;
            while (read(input[0], &ch, 1) > 0) { //当将管道中的数据读完后循环再进行read管道的话就会出现阻塞，但是子进程会退出
                response_body.push_back(ch);
            }
            //等待子进程
            int status = 0;
            pid_t ret = waitpid(pid, &status, 0);
            if (ret == pid) {
                if (WIFEXITED(status)) {   //子进程cgi正常退出
                    if (WEXITSTATUS(status) == 0) {
                        code = OK;
                    }
                    else {
                        code = BAD_REQUEST;//非正常退出，通过信号退出(用户传递的参数在cgi程序中解析错误，或运算错误，)
                    }
                }
            }
            else {
                code = SERVER_ERROR;
            }

            close(input[0]);
            close(output[1]);
        }
        return code;
    }
    int ProcessNonCgi()
    {
        //该函数中只需要打开需要返回brower的文件即可
        http_response.fd = open(http_request.path.c_str(), O_RDONLY); //如果request.path资源打开失败则直接返回
        if (http_response.fd >= 0) {
            return OK;
        }
        return NOT_FOUND;
    }
    void HandlerOk()
    {
        //构建响应报头
        std::string header_line = "Content-Type: ";
        header_line += Suffix2Desc(http_request.suffix);
        header_line += LINE_END;
        http_response.response_header.push_back(header_line);

        header_line = "Content-Length: ";
        if (http_request.cgi) {
            header_line += std::to_string(http_response.response_body.size());
        }
        else {
            // struct stat s;
            // stat(http_request.path.c_str(),&s);
            // auto size = s.st_size;
            header_line += std::to_string(http_response.size);
        }
        header_line += LINE_END;
        http_response.response_header.push_back(header_line);
        http_response.response_header.push_back(LINE_END);
    }
    void HandlerError(const std::string& path)
    {
        //如果在ProcessCgi()函数中出现错误返回，致使code = 404则此时就需要将http_request.cgi的值改为false
        http_request.cgi = false;
        http_response.fd = open(path.c_str(), O_RDONLY); //这个文件描述符需要在下面的SendHttpResponse方法中使用

        //std::cout << "RESPONSE FD IS: " << http_response.fd << std::endl;

        if (http_response.fd > 0) { //打开服务器要返回的错误资源文件就可以构建对应的响应报头了
            struct stat s;
            stat(path.c_str(), &s);
            auto& size = http_response.size;
            size = s.st_size;
            std::string header_line = "Content-Type: ";
            header_line += Suffix2Desc(".html");
            header_line += LINE_END;
            http_response.response_header.push_back(header_line);

            header_line = "Content-Length: ";
            header_line += std::to_string(size);
            header_line += LINE_END;
            http_response.response_header.push_back(header_line);
        }

    }
    void HandlerHttpResponseHelper()
    {
        auto& code = http_response.status_code;
        //统一构建响应行
        auto& status_line = http_response.status_line;
        status_line = HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code);
        status_line += " ";
        status_line += Code2Desc(code);

        std::string path = WEB_ROOT;
        path += "/";
        switch (code)
        {
        case OK:
            HandlerOk();
            break;
        case NOT_FOUND:
            path += PAGE_404;
            HandlerError(path);
            break;
        case BAD_REQUEST:
            path += PAGE_400;
            HandlerError(path);
            break;
        case SERVER_ERROR:
            path += PAGE_500;
            HandlerError(path);
            break;
        default:
            break;
        }
    }

public:
    EndPoint(int _sock)
        : sock(_sock)
        , stop(false)
    {
    }
    ~EndPoint()
    {
        close(sock);
    }
    void RecvHttpRequest()
    {
        if (!RecvHttpRequestLine() && !RecvHttpRequestHeader()) {
            ParseHttpRequestLine(); //分析请求行
            ParseHttpRequestHeader(); //分析请求报头
            RecvHttpRequestBody(); //接收请求报文
        }                 //接收请求行 //接收请求报头
    }
    // void ParseHttpRequest() // parse：分析句法
    // {
    //     ParseHttpRequestLine();
    //     ParseHttpRequestHeader();
    // }
    void BuildHttpResponse()
    {
        auto& status_code = http_response.status_code;
        const auto& method = http_request.method;
        auto& path = http_request.path;//不在goto 语句之间初始化变量，可以声明变量
        struct stat st;
        //用于记录响应报头suffix字符串在request.path中的起始位置
        size_t pos;
        if (method != "GET" && method != "POST") {
            status_code = NOT_FOUND;
            LOG(WARNING, "method is not right");
            goto END;
        }
        if ("GET" == method) { //当请求方法是GET时，请求参数数据再uri中
            size_t pos = http_request.uri.find("?");
            if (pos == std::string::npos) {
                http_request.path = http_request.uri;
            }
            else {
                Util::CutString(http_request.uri, http_request.path, http_request.query_string, "?");
                http_request.cgi = true;
            }
        }
        else if ("POST" == method) {
            http_request.path = http_request.uri;
            http_request.cgi = true;
        }
        //服务器需要将请求行中的uri转换为自己wwwroot目录下的资源，即向http_reqeust.path前添加wwwroot
        path = WEB_ROOT + path;
        if ('/' == path[path.length() - 1]) {
            path += HOME_PAGE;
        }
        std::cout << "debug：" << http_request.path << std::endl;
        if (0 == stat(http_request.path.c_str(), &st)) {
            //说明资源在服务器端存在
            if (S_ISDIR(st.st_mode)) {
                //说明此时浏览器发来的请求中的uri的要访问的资源路径是一个目录
                //目录是不可能一'/'结尾的
                path += "/";
                path += HOME_PAGE;
                stat(http_request.path.c_str(), &st); //如果path是目录的话，加上服务器默认的页面需要重新进行stat获取文件的属性
            }
            http_response.size = st.st_size;
            //如果浏览器请求的资源是一个可被执行的就需要进行特殊处理
            if (st.st_mode & S_IXUSR || st.st_mode & S_IXGRP || st.st_mode & S_IXOTH) {
                //特殊处理
                http_request.cgi = true;
            }
        }
        else {
            //说明资源不存在
            auto info = http_request.path;
            info += " NOT FOUND!";
            LOG(WARNING, info);
            status_code = NOT_FOUND;
            goto END;
        }

        //分析得到请求中后缀用于接下来构建响应报头
        pos = http_request.path.rfind(".");
        if (std::string::npos == pos) {
            http_request.suffix = ".html";
        }
        else {
            http_request.suffix = http_request.path.substr(pos);
        }
        //进行CGI机制部分
        if (http_request.cgi) {
            status_code = ProccessCgi();
        }
        else {
            status_code = ProcessNonCgi();//简单的网页返回，返回静态网页   //没有参数、httprequest中的
        }
    END:
        // if(status_code != OK){

        // }
        //统一处理错误
        HandlerHttpResponseHelper();
        return;
    }

    void SendHttpResponse()
    {
        send(sock, http_response.status_line.c_str(), http_response.status_line.size(), 0);
        send(sock, http_response.blank.c_str(), http_response.blank.size(), 0);
        for (const auto& header_line : http_response.response_header) {
            send(sock, header_line.c_str(), header_line.size(), 0);
        }
        if (http_request.cgi) {
            size_t size = http_response.response_body.length();
            std::cout << "response_body.length：" << http_response.response_body.length() << std::endl;
            std::cout << http_response.response_body << std::endl;
            size_t total = 0;
            int n = 0;
            while ((total < size) && (n = send(sock, http_response.response_body.c_str() + total, size - total, 0)) > 0) {
                total += n;
            }
        }
        else {
            std::cout << "RESPONSE FD IS: " << http_response.fd << std::endl;
            sendfile(sock, http_response.fd, nullptr, http_response.size);
            std::cout << "发送数据成功! 发送了：" << http_response.size << std::endl;
        }
        //必须要关闭http_request.path对应的文件描述符
        close(http_response.fd);
    }

    bool getStop()
    {
        return stop;
    }
};

// 线程执行函数
class CallBack
{
public:
    void operator()(int sock)
    {
        HandleRequest(sock);
    }
    void HandleRequest(int sock) // 类中的静态成员函数没有this指针
    {
        LOG(INFO, "Hanlde Request begin");
        // int sock = *reinterpret_cast<int*>(_sock);
        // int sock = *(int *)_sock;
        // delete (int *)_sock; //在这里先将_sock设置为一个线程栈内的变量，然后直接delete，避免close _sock的时候delete

        // std::cout << "get a new link:" << sock << std::endl;
        // std::string line;
        // Util::ReadLine(sock,line);//读取请求行
        // std::cout << line << std::endl;
//#define DEBUG 1
#ifdef DEBUG
        std::string buffer;
        buffer.resize(4096);
        int n = recv(sock, const_cast<char*>(buffer.c_str()), buffer.size() * sizeof(char), 0);
        std::cout << "-----------begin-----------" << std::endl;
        std::cout << buffer << std::endl;
        std::cout << "-----------end-----------" << std::endl;
#else
        EndPoint* ep = new EndPoint(sock);
        ep->RecvHttpRequest();
        //ep->ParseHttpRequest();
        if (!ep->getStop()) {
            LOG(INFO, "Recv NO Error, Begin Bulid And Send");
            ep->BuildHttpResponse();
            ep->SendHttpResponse();
        }
        else {
            LOG(INFO, "Recv Error, Stop Bulid And Send");
        }
        delete ep;
        ep = nullptr;
#endif
        LOG(INFO, "Handle Request end");
    }
};